package com.base.monitor.module.biz.message.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.base.monitor.framework.exception.CustomerException;
import com.base.monitor.framework.util.CustomerUtils;
import com.base.monitor.model.constant.CollectConstant;
import com.base.monitor.model.enums.biz.BrightSkyFireControlMainEngineCodeEnum;
import com.base.monitor.model.enums.biz.CollectTypeEnum;
import com.base.monitor.model.enums.code.impl.common.ParamCode;
import com.base.monitor.model.enums.code.impl.common.ResultCode;
import com.base.monitor.model.model.biz.cfg.entity.Gateway;
import com.base.monitor.model.model.biz.cfg.vo.IndicatorCommandVo;
import com.base.monitor.model.model.biz.collect.entity.CollectData;
import com.base.monitor.module.biz.context.CfgContext;
import com.base.monitor.module.biz.context.ClientContext;
import com.base.monitor.module.biz.handler.HandleCollectDataHandler;
import com.base.monitor.module.biz.handler.PushFireControlUpdateTimeHandler;
import com.base.monitor.module.biz.message.Message;
import com.base.monitor.module.biz.util.ChannelUtils;
import com.base.monitor.module.biz.util.ConvertUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 蓝天消防主机的指标消息
 */
@Component
@Slf4j
public class BrightSkyFireControlIndicatorMessage implements Message {

    @Autowired
    private HandleCollectDataHandler handleCollectDataHandler;

    /**
     * 消息类型
     *
     * @return
     */
    @Override
    public String messageType() {
        return CollectConstant.MESSAGE_TYPE_IS_BRIGHT_SKY_FIRE_CONTROL_INDICATOR;
    }

    /**
     * 处理消息
     *
     * @param ctx     netty客户端通道信息
     * @param message 消息内容
     */
    @Override
    public void handle(ChannelHandlerContext ctx, String message) {
//        message = "23F00E100500061003100000020000000000000001300E";
        log.info("[蓝天消防]处理ip为{}的消防主机指标消息，消息内容为{}", ctx.channel().remoteAddress(), message);

        // 1.查找是否已经连接过，没有连接过要先注册
        connectedIfAbsent(ctx);

        // 2.解析消息
        CollectData collect = parseFireControlMessage(ctx, message);

        // 3.更新网关和指标的在线状态
        Gateway gateway = updateOnlineTime(ctx, collect);

        // 4.推送给远端
        pushCollect(collect, gateway.getGatewayId());
    }

    /**
     * 更新网关和指标的在线时间
     * @param ctx 信道上下文
     * @param collect 采集的数据
     * @return 对应的网关信息
     */
    private Gateway updateOnlineTime(ChannelHandlerContext ctx, CollectData collect) {
        // 1.更新网关在线时间
        Gateway gateway = getGatewayByIp(ChannelUtils.formatChannelIp(ctx.channel().remoteAddress().toString()));
        CfgContext.addGatewayOnlineTime(gateway.getGatewayId());

        // 2.更新指标在线时间
        List<IndicatorCommandVo> indicators = gateway.getIndicators();
        if (CollectionUtil.isEmpty(indicators)) {
            return gateway;
        }
        List<String> ids = indicators.stream().map(IndicatorCommandVo::getId).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(indicators)) {
            return gateway;
        }
        for (String id : ids) {
            CfgContext.addIndicatorOnlineTime(id);
        }
        return gateway;
    }

    /**
     * 推送给远端
     * @param collect 需要推送的数据
     * @param gatewayId 网关id
     */
    private void pushCollect(CollectData collect, Long gatewayId) {
        // CRT相当于心跳，数据不会发送出去
        if (checkCRT(collect)) {
//            log.info("[蓝天消防]该消防消息:{}为心跳，不推送到远端", collect);
            PushFireControlUpdateTimeHandler.addFireControlOnlineTimeMap(gatewayId);
            return;
        }
        log.info("[蓝天消防]该消防消息:{}为报警消息，需要推送到远端", collect);
        handleCollectDataHandler.addTask(collect);
    }

    /**
     * 判断是否为CRT消息
     * @param collect 采集指令解析后的消息
     * @return
     */
    private Boolean checkCRT(CollectData collect) {
        return collect.getValue().get(0).equals(BrightSkyFireControlMainEngineCodeEnum.ON_SITE_INSPECTION_CRT.getMessage());
    }

    /**
     * 如果该信道不存在的话就要注册信道
     * @param ctx 信道上下文
     */
    private void connectedIfAbsent(ChannelHandlerContext ctx) {
        // 1.获取ip地址
        String ip = ChannelUtils.formatChannelIp(ctx.channel().remoteAddress().toString());

        ClientContext.ChannelInfo channelInfo = ClientContext.getChannelInfoByIp(ip);
        if (channelInfo == null) {
            // 2.生成连接命令。因为消防主机是没有连接命令的
            String connect = manualGenerateConnectMessage();

            // 3.注册信道
            ClientContext.addClient(connect, ctx);
        }
    }


    /**
     * 手动生成连接命令
     * @return
     */
    private String manualGenerateConnectMessage() {
        String message = generateConnectMessage();
        if (ClientContext.getClientMap().containsKey(message)) {
            message = manualGenerateConnectMessage();
        }
        return message;
    }

    /**
     * 生成随机的连接命令
     * @return
     */
    private String generateConnectMessage() {
        return "[蓝天消防]" + CustomerUtils.getRandomString(5);
    }

    /**
     * 解析消防主机的消息
     * @param ctx 信道上下文
     * @param message 消防主机的消息
     * @return
     */
    private CollectData parseFireControlMessage(ChannelHandlerContext ctx, String message) {
        // 获取报警类型
        String alarmTypeHex = message.substring(2, 6);

        // 获取设备编号，即指标编号
        String indicatorHex = message.substring(26, 38);

        // 解析报警类型
        String alarmType = parseAlarmTypeHex(alarmTypeHex);

        String indicatorId = parseIndicatorHex(indicatorHex);

        // 组装并返回采集对象
        return assembleAndReturn(indicatorId, alarmType);
    }

    /**
     * 根据indicatorHex的值解析出内部编码，即指标id
     * @param indicatorHex 待解析的十六进制
     * @return
     */
    private String parseIndicatorHex(String indicatorHex) {
        String machineHex = indicatorHex.substring(0, 4);
        String areaHex = indicatorHex.substring(4, 8);
        String locationHex = indicatorHex.substring(8, 12);

        Integer machineId = calculateSumForTwoBitStringHex(machineHex);
        Integer areaId = calculateSumForTwoBitStringHex(areaHex);
        String locationId = calculateSumForTwoBitStringHex(locationHex) + "";

        locationId = CustomerUtils.codeAddOne(locationId, 3);
        return machineId + "-" + areaId + "-" + locationId;
    }

    /**
     * 组装并返回采集对象
     * @param indicatorId 指标id
     * @param alarmType 报警种类
     * @return
     */
    private CollectData assembleAndReturn(String indicatorId, String alarmType) {
        List<String> values = new ArrayList<>();
        values.add(alarmType);
        return new CollectData(indicatorId, CollectTypeEnum.FIRE, values);
    }

    /**
     * 解析报警类型
     * @param alarmTypeHex 报警类型对应的十六进制
     * @return
     */
    private String parseAlarmTypeHex(String alarmTypeHex) {
        if (StrUtil.isBlank(alarmTypeHex) || alarmTypeHex.length() != 4) {
            throw new CustomerException(ParamCode.PARAM_IS_INVALID, "解析报警类型时，报警类型不合法");
        }

        int result = calculateSumForTwoBitStringHex(alarmTypeHex);

        return BrightSkyFireControlMainEngineCodeEnum.getMessageFromCode(result + "");
    }

    private int calculateSumForTwoBitStringHex(String hex) {
        String highHex = hex.substring(0, 2);
        String lowHex = hex.substring(2, 4);

        int high = ConvertUtils.hexToDec(highHex);
        int low = ConvertUtils.hexToDec(lowHex);

        return high + low;
    }

    /**
     * 根据ip获取指标id
     * @param ip 信道ip地址
     * @return
     */
    private Gateway getGatewayByIp(String ip) {
        List<Gateway> gatewayVoList = CfgContext.getGatewayVoList();
        if (CollectionUtil.isEmpty(gatewayVoList)) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "没有找到这条消息对应的指标");
        }
        List<Gateway> collect = gatewayVoList.stream().filter(item -> item.getGatewayIp().equals(ip)).collect(Collectors.toList());
        if (CollectionUtil.isEmpty(collect)) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "没有找到这条消息对应的指标");
        }
        return collect.get(0);
    }
}
